﻿using CKS.Core.Framework.Models;
using CKS.PLC.Core;
using CKS.PLC.Core.Event;
using CKS.Core.Framework;
using Infrastructure;
using IP2Region.Ex.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using S7.Net;
using SqlSugar;
using SqlSugar.Extensions;
using SqlSugar.IOC;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using ZR.Model.Models.ProductionControl;


namespace CKS.PLC.Core
{
    /// <summary>
    /// plc客户端
    /// 主要功能，读取订阅点信息，调用处理类进行订阅点的处理
    /// </summary>
    public class PlcClient : IPlcClient
    {
        public PlcConfig PlcInfo { get; set; }
        IServiceProvider serviceProvider;
        ILogger<PlcClient> logger;
        ISqlSugarClient db;
        IFreeSql<MesDB> fsql = null;
        public PlcClient(PlcConfig _config, IServiceProvider _serviceProvider)
        {
            this.PlcInfo = _config;
            serviceProvider = _serviceProvider;
            logger = serviceProvider.GetService<ILogger<PlcClient>>();
            fsql = serviceProvider.GetService<IFreeSql<MesDB>>();
            try
            {
                db = DbTransient.Sugar;

                //EventBus.AddLog($"{_config.WorkstationCode} MySql: {db.ContextID}");
            }
            catch (Exception ex)
            {
                EventBus.AddLog($"{_config.WorkstationCode} Init Mysql exception: {ex.Message}");
            }

        }

        public Plc plcSession;

        private List<CksPlcDataPosition> AllDataPosition = new List<CksPlcDataPosition>();       //所有点位
        private List<CksPlcDataPosition> SubscriptionPosition = new List<CksPlcDataPosition>();  //订阅点位
        private DateTime LastHeartTime = DateTime.Now;


        public class DataBlock
        {
            public DataBlock(int blockDb, int blockLenth, Task<byte[]> readTask, SieminDecoder decoder)
            {
                BlockDb = blockDb;
                BlockLenth = blockLenth;
                ReadTask = readTask;
                Decoder = decoder;
            }

            public int BlockDb { get; set; }
            public int BlockLenth { get; set; }
            public Task<byte[]> ReadTask { get; set; }
            public SieminDecoder Decoder { get; set; }
        }


        /// <summary>
        /// BlockDb 数据块 
        /// BlockLenth 大小
        /// Task<byte[]> 异步读取的结果
        /// </summary>
        private List<DataBlock> DataBlocks = new List<DataBlock>();   // 需要读取的数据块

        /// <summary>
        /// 启动客户端
        /// </summary>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public async Task StartAsync(CancellationToken cancellation)
        {
            await Task.Yield();

            try
            {
                UpdatePlcSession();
                BindCacheData();
                await Execute(cancellation);
                await Task.CompletedTask;
            }
            catch (Exception ex)
            {

            }


        }


        public void UpdatePlcSession()
        {
            plcSession = PlcInfo.CpuType switch
            {
                "1500" => new Plc(CpuType.S71500, PlcInfo.PlcAddress, 0, 1),
                _ => new Plc(CpuType.S71200, PlcInfo.PlcAddress, 0, 1)
            };
            plcSession.ReadTimeout = 2000;
            plcSession.WriteTimeout = 2000;
        }


        private void BindCacheData()
        {
            if (!PlcInfo.IsEnabled)
                return;
            //获取需要读取的数据块
            AllDataPosition = db.Queryable<CksPlcDataPosition>().Where(a => a.PlcId == PlcInfo.Id && a.Module == 1).ToList();
            SubscriptionPosition = AllDataPosition.FindAll(a => a.IsListen || a.EffectType == 3);
            //提取订阅点的数据块批量读取
            DataBlocks.Clear();
            foreach (var item in SubscriptionPosition)
            {
                var findex = DataBlocks.FindIndex(a => a.BlockDb == item.BlockDb);
                if (findex < 0)
                {
                    //该DB块内的所有数据
                    var points = AllDataPosition.FindAll(a => a.BlockDb == item.BlockDb);
                    DataBlocks.Add(new(item.BlockDb, item.BlockLength, null, new SieminDecoder(points)));
                }
            }
        }

        public async Task ConnectPlc(CancellationToken cancellation)
        {
            try
            {
                if (!plcSession.IsConnected)
                {
                    await plcSession.OpenAsync(cancellation);
                }
                if (IsConnected == false && plcSession.IsConnected)
                {
                    EventBus.AddLog($"{PlcInfo.WorkstationCode}:{plcSession.IP} 连接成功");
                }
                IsConnected = plcSession.IsConnected;
            }
            catch (Exception ex)
            {
                if (IsConnected)
                {
                    EventBus.AddLog($"{PlcInfo.WorkstationCode}:{plcSession.IP} 连接已断开");
                }
                //logger.LogWarning($"plc:{plcSession.IP}连接异常 {ex.Message}");
                IsConnected = false;
                if (!PlcInfo.PlcAddress.Equals(plcSession.IP))
                {
                    EventBus.AddLog($"{PlcInfo.WorkstationCode} PLC/IP变化");
                    UpdatePlcSession();
                }
                Debug.WriteLine($"{PlcInfo.WorkstationCode} 连接失败 {DateTime.Now.ToString("HH:mm:ss.fff")}"); ;
            }
        }

        /// <summary>
        /// 每500毫秒读取一次
        /// </summary>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public async Task Execute(CancellationToken cancellation)
        {

            try
            {
                while (!cancellation.IsCancellationRequested)
                {
                    //检查当前PLC状态是否禁用 
                    //检查当前PLC的通信点位是否有变化，有变化需要重新加载
                    int STEP = 1;
                    try
                    {
                        //EventBus.AddLog($"{plcConfig.WorkstationCode} MySql: {db.ContextID}");
                        var model = db.Queryable<PlcConfig>().Where(a => a.Id == PlcInfo.Id).First();
                        if (model == null || model.IsEnabled == false)
                        {
                            await Task.Delay(1000, cancellation);
                            continue;
                        }
                        PlcInfo.IsEnabled = model.IsEnabled;
                        PlcInfo.PlcAddress = model.PlcAddress;
                        bool pointIsChange = false;
                        var alls = db.Queryable<CksPlcDataPosition>().Where(a => a.PlcId == PlcInfo.Id && a.Module == 1).ToList();
                        if (alls.Count != AllDataPosition.Count)
                            pointIsChange = true;
                        else if (alls.Count > 0)
                        {
                            for (int i = 0; i < alls.Count; i++)
                            {
                                var id = alls[i].Id;
                                var a = alls[i];
                                var b = AllDataPosition.Find(x => x.Id == id);
                                if (b == null)
                                    pointIsChange = true;
                                else if (b.BytePosition != a.BytePosition)
                                    pointIsChange = true;
                                else if (b.BlockDb != a.BlockDb)
                                    pointIsChange = true;
                                else if (!b.ParseType.Equals(a.ParseType))
                                    pointIsChange = true;
                                else if (b.EffectType != a.EffectType)
                                    pointIsChange = true;
                                else if (b.ActionId != a.ActionId || b.DataId != a.DataId || b.LinkActionId !=a.LinkActionId)
                                    pointIsChange = true;
                            }
                        }
                        else
                        {//没有点位数据
                            await Task.Delay(1000, cancellation);
                            continue;
                        }
                        if (pointIsChange)
                        {
                            BindCacheData();
                            EventBus.AddLog($"{PlcInfo.WorkstationCode}重新加载了点位");
                        }

                        STEP = 2;
                        await ConnectPlc(cancellation);
                        STEP = 3;
                        DoHeart();
                        STEP = 4;
                        await ExecuteMain();
                    }
                    catch (Exception ex)
                    {

                        EventBus.AddLog($"{PlcInfo.WorkstationCode} STEP:{STEP} 异常{ex.GetType()}: {ex.Message}");
                        if (ex is SqlSugarException || ex.Source == "MySqlConnector")
                        {
                            EventBus.AddLog($"{PlcInfo.WorkstationCode} STEP:{STEP} 数据库访问异常，重置数据库对象");
                            new Action(() => { db.Dispose(); }).CatchAction();
                            new Action(() => { db = DbTransient.Sugar; }).CatchAction();

                        }
                    }


                    await Task.Delay(30, cancellation);
                }
                await Task.CompletedTask;
            }
            catch (Exception ex)
            {
                logger.LogWarning($"异常 {ex.Message}");
            }


        }






        int HeartSeed = 1;
        int PlcLastHeart = -1;
        int plcHeartValue = 0;
        public bool IsConnected { get; set; } = false;
        bool LastConnected = false;
        public void DoHeart()
        {

            try
            {
                var ts = DateTime.Now - LastPlcHeart;
                if (ts.TotalSeconds < 5)
                {
                    IsOnline = true;
                    App.AddOnlinePlcs(PlcInfo.Id);
                }
                else
                {
                    IsOnline = false;
                    App.RemoveOnlinePlcs(PlcInfo.Id);
                }


                ts = DateTime.Now - LastHeartTime;
                if (ts.TotalMilliseconds > 500)
                {
                    HeartSeed++;
                    if (HeartSeed > 1000)
                        HeartSeed = 1;
                    Int16 valueToWrite = (short)(HeartSeed % 2);
                    //查询心跳点位
                    var point = AllDataPosition.Find(a => a.EffectType == 2);
                    if (point != null && plcSession.IsConnected)
                    {
                        plcSession.Write(DataType.DataBlock, point.BlockDb, (int)point.BytePosition, valueToWrite);
                        LastHeartTime = DateTime.Now;
                        //读取PLC心跳值
                        var plcheart = AllDataPosition.Find(a => a.EffectType == 3);
                        if (plcheart != null)
                            plcHeartValue = (short)plcSession.Read(DataType.DataBlock, plcheart.BlockDb, (int)plcheart.BytePosition, VarType.Int, 1);
                    }

                    if (PlcLastHeart != plcHeartValue || IsConnected != LastConnected)
                    {
                        //logger.Information($"变更心跳值{plcHeartValue}");
                        // EventBus.PlcHeart(this, plcHeartValue);
                        LastPlcHeart = DateTime.Now;
                        EventBus.PlcHeart(this, plcHeartValue);
                    }
                    PlcLastHeart = plcHeartValue;
                    LastConnected = IsConnected;
                }
            }
            catch (Exception ex)
            {
                logger.LogWarning($"{PlcInfo.WorkstationCode} 写入MES心跳异常 {ex.Message}");
            }
        }





        /// <summary>
        /// 执行订阅方法
        /// </summary>
        public void ExecuteSubscriptionAction(DecodeValue value)
        {
            try
            {
                var ActionBaseType = PlcActionDLLManage.GetActionBaseType();
                // 获取方法
                MethodInfo method = ActionBaseType.GetMethod("GetInstance");

                var instance = method.Invoke(null, [value]) as BasePlcAction;
                instance.InitConfig(serviceProvider, db);
                try
                {
                    instance.Execute();
                }
                catch (Exception)
                {
                    throw;
                }
                finally
                {
                    instance.Close();
                }


            }
            catch (Exception ex)
            {
                EventBus.AddLog($"工站:{PlcInfo.WorkstationCode}执行异常，订阅点{value.Position.BlockDb}-{value.Position.ActionId}:{ex.Message}");
                throw;
            }
        }

        DateTime LastPrintElapsedTime = DateTime.Now;
        DateTime LastReadTime = DateTime.Now;
        /// <summary>
        /// 读取订阅点位
        /// </summary>
        /// <returns></returns>
        public async Task ExecuteMain()
        {
            string errmsg = "";
            try
            {
                if (!plcSession.IsConnected)
                { return; }
                if (DataBlocks.Count == 0)
                { return; }



                //获取需要读取的数据块
                // siemenPlc.ReadBytesAsync(DataType.DataBlock, item.Key, 0, item.Value);
                List<Task<byte[]>> tasks = new List<Task<byte[]>>();

                Stopwatch stopwatch = Stopwatch.StartNew();
                for (int i = 0; i < DataBlocks.Count; i++)
                {
                    var block = DataBlocks[i];
                    errmsg = $"正在读取DB{block.BlockDb}";
                    var task = plcSession.ReadBytesAsync(DataType.DataBlock, block.BlockDb, 0, block.BlockLenth);
                    block.ReadTask = task;
                    tasks.Add(task);
                }
                await Task.WhenAll(tasks);
                errmsg = string.Empty;
                stopwatch.Stop();
                if ((DateTime.Now - LastPrintElapsedTime).TotalSeconds > 5)
                {
                    var ts = DateTime.Now - LastReadTime;
                    // EventBus.AddLog($" plc:{plcConfig.Id} read:{stopwatch.ElapsedMilliseconds}ms interval:{(int)ts.TotalMilliseconds}ms");
                    LastPrintElapsedTime = DateTime.Now;
                }
                LastReadTime = DateTime.Now;
                // 
                //
                foreach (var block in DataBlocks)
                {
                    var blockBytes = block.ReadTask.Result; //异步任务结果
                                                            //解析所有点位数据
                    var DecodeValues = block.Decoder.Decode(blockBytes);
                    var subscriptionCodes = DecodeValues
                        .FindAll(a => a.Position.IsListen);
                    foreach (var val in subscriptionCodes)
                    {
                        val.AllValues = DecodeValues;
                        val.ElapsedMilliseconds = stopwatch.ElapsedMilliseconds;
                        if (val.IsChange || val.Position.EffectType == 4)
                        {

                            val.plcSession = plcSession;
                            val.SiemensClient = this;
                            val.AllPositions = AllDataPosition;

                            val.LinkPositions = AllDataPosition.FindAll(a => a.LinkActionId == val.Position.ActionId);
                            //  ProccessPointValue(val);

                            ExecuteSubscriptionAction(val);

                            // EventBus.AddLog($" plc:{plcConfig.Id} 处理动作耗时:{stopwatch.ElapsedMilliseconds}ms");
                            val.OldValue = val.Value; //处理完成后将值赋给旧值变量

                        }
                    }

                }
            }
            catch (Exception ex)
            {
                logger.LogWarning($"工站:{PlcInfo.WorkstationCode}异常，{errmsg}, {ex.Message} {ex.StackTrace}");
                await Task.Delay(1000);

            }
        }




        private DateTime LastPlcHeart = App.MinTime;
        /// <summary>
        /// 当前PLC是否在线
        /// </summary>
        public bool IsOnline { get; set; }


        /// <summary>
        /// 处理订阅点位变化信息
        /// </summary>
        /// <param name="value"></param>
        public void ProccessPointValue(DecodeValue value)
        {
            //如果订阅点的数据是1：执行操作， 
            //否则执行复位操作

            //ActionBase2 act = ActionBase2.GetInstance(value);
            //act.InitConfig(serviceProvider);
            //act.Execute();

        }

        public int? ReadInt16(int db, int startByteAdr)
        {
            try
            {
                return (int)plcSession.Read(DataType.DataBlock, db, startByteAdr, VarType.Int, 1);
            }
            catch (Exception)
            {
                return null;
            }
        }
    }
}
